package com.venusic.handwrite.view;

import com.venusic.handwrite.glide.SignFileOutputStream;
import com.venusic.handwrite.view.point.*;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.data.rdb.ValuesBucket;
import ohos.media.image.ImagePacker;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageInfo;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;
import ohos.media.photokit.metadata.AVStorage;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;
import ohos.utils.net.Uri;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class HandWriteView extends Component implements Component.TouchEventListener, Component.DrawTask {

    List<TimedPoint> points = new ArrayList<>();
    Stack<TimedPoint> cachePoints = new Stack<>();
    PointUtil pointUtil = new PointUtil();
    private PixelMap mPixelMap;
    private ohos.agp.render.Canvas mCanvas;
    private ohos.agp.render.Paint mPaint;
    private boolean isSign = false;

    private final int mBackColor = Color.getIntColor("#00000000");

    public HandWriteView(Context context) {
        this(context, null);
        Init();
    }

    public HandWriteView(Context context, AttrSet attrs) {
        this(context, attrs, null);
        Init();
    }

    public HandWriteView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        Init();
    }

    private void Init() {
        int maxWidth = 16;
        int minWidth = 8;
        Color paintColor = Color.RED;
        pointUtil.setWidth(minWidth, maxWidth);
        mPaint = new ohos.agp.render.Paint();
        mPaint.setColor(paintColor);
        mPaint.setStrokeWidth(10);
        mPaint.setDither(true);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(ohos.agp.render.Paint.Style.STROKE_STYLE);
        mPaint.setStrokeCap(ohos.agp.render.Paint.StrokeCap.ROUND_CAP);
        mPaint.setStrokeJoin(ohos.agp.render.Paint.Join.ROUND_JOIN);

        addDrawTask(this::onDraw);
        setTouchEventListener(this::onTouchEvent);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        MmiPoint pointerPosition = touchEvent.getPointerPosition(touchEvent.getIndex());
        float x = pointerPosition.getX();
        float y = pointerPosition.getY();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                //移植时未找到对应的鸿蒙方法，安卓测试该方法无明显影响，故暂注释等后期方法的补全
//                getComponentParent().requestDisallowInterceptTouchEvent(true);
                points.clear();
                addPoint(getNewPoint(x, y));
                break;
            case TouchEvent.POINT_MOVE:
                isSign = true;
                    addPoint(getNewPoint(x, y - 300));
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                addPoint(getNewPoint(x, y));
//                getComponentParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        invalidate();
        return true;
    }

    private TimedPoint getNewPoint(float x, float y) {
        if (cachePoints.empty()) {
            return new TimedPoint(x, y);
        } else return cachePoints.pop().set(x, y);
    }

    private void recyclePoint(TimedPoint point) {
        cachePoints.push(point);
    }

    @Override
    public void onDraw(Component component, ohos.agp.render.Canvas canvas) {
        if (mPixelMap != null) {
            canvas.drawPixelMapHolder(new ohos.agp.render.PixelMapHolder(mPixelMap), 0, 0, mPaint);
        }
    }

    private void addPoint(TimedPoint point) {
        points.add(point);
        if (points.size() > 3) {
            ensureSignaturePixelMap();
            TimedPoint s0 = points.get(0);
            TimedPoint s1 = points.get(1);
            TimedPoint s2 = points.get(2);
            TimedPoint s3 = points.get(3);
            float cx1 = s1.x + (s2.x - s0.x) / 4;
            float cy1 = s1.y + (s2.y - s0.y) / 4;
            float cx2 = s2.x - (s3.x - s1.x) / 4;
            float cy2 = s2.y - (s3.y - s1.y) / 4;
            pointUtil.set(s1, getNewPoint(cx1, cy1), getNewPoint(cx2, cy2), s2);
            float originalWidth = mPaint.getStrokeWidth();
            float drawSteps = (float) Math.floor(pointUtil.length());
            for (int i = 0; i < drawSteps; i++) {
                float t = (float) i / drawSteps;
                DrawPoint drawPoint = pointUtil.calculate(t);
                mPaint.setStrokeWidth(drawPoint.width);
                mCanvas.drawPoint(drawPoint.x, drawPoint.y, mPaint);
            }
            mPaint.setStrokeWidth(originalWidth);
            recyclePoint(points.remove(0));
            recyclePoint(pointUtil.control1);
            recyclePoint(pointUtil.control2);
        } else if (points.size() == 1) {
            points.add(getNewPoint(point.x, point.y));
        }
    }

    private void ensureSignaturePixelMap() {
        if (mPixelMap == null) {
            PixelMap.InitializationOptions opt = new PixelMap.InitializationOptions();
            opt.size = new Size(getWidth(), getHeight());
            opt.pixelFormat = PixelFormat.ARGB_8888;
            mPixelMap = PixelMap.create(opt);
            mCanvas = new ohos.agp.render.Canvas(new ohos.agp.render.Texture(mPixelMap));
        }
    }


    /**
     * 保存画板
     *
     * @param path 保存到路劲
     */

    public void save(String path,Context context) throws IOException {
        save(path, false, 0, false,context);
    }

    public void save(String path, boolean isEncrypt,Context context) throws IOException {
        save(path, false, 0, isEncrypt,context);
    }

    /**
     * 保存画板
     *
     * @param path       保存到路径
     * @param clearBlank 是否清楚空白区域
     * @param blank      边缘空白区域
     * @param isEncrypt  加密存储，选择加密存储会自动追加后缀为.sign
     */
    public void save(String path, boolean clearBlank, int blank, boolean isEncrypt,Context context) throws IOException {
        PixelMap pm = mPixelMap;
        if (clearBlank) {
            pm = clearBlank(pm, blank);
        }
        if (isEncrypt) path = path + ".sign";

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImagePacker imagePacker = ImagePacker.create();
        ImagePacker.PackingOptions packingOptions = new ImagePacker.PackingOptions();
        packingOptions.format = "image/jpeg";
        packingOptions.quality = 100;
        boolean result = imagePacker.initializePacking(bos, packingOptions);
        if (result){
            result = imagePacker.addImage(pm);
            if (result){
                long dataSize = imagePacker.finalizePacking();
            }
        }
        byte[] buffer = bos.toByteArray();
        if (buffer != null) {
            File file = new File(path);
            if (file.exists()) {
                file.delete();
            }
            OutputStream outputStream = isEncrypt ? new SignFileOutputStream(file) : new FileOutputStream(file);
            outputStream.write(buffer);
            outputStream.close();
        }

        if (!isEncrypt){
            //写入照片目录
            try {
                ValuesBucket valuesBucket = new ValuesBucket();
                valuesBucket.putString(AVStorage.Images.Media.DISPLAY_NAME,path);
                valuesBucket.putString("relative_path","DCIM/");
                valuesBucket.putString(AVStorage.Images.Media.MIME_TYPE,"image/JPEG");
                //应用独占
                valuesBucket.putInteger("is_pending",1);
                DataAbilityHelper helper = DataAbilityHelper.creator(context);
                int id = helper.insert(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, valuesBucket);
                Uri uri = Uri.appendEncodedPathToUri(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(id));
                //需要”w“写权限
                FileDescriptor fd = helper.openFile(uri, "w");
                FileOutputStream fos = new FileOutputStream(fd);
                boolean result1 = imagePacker.initializePacking(fos, packingOptions);
                if (result1){
                    result1 = imagePacker.addImage(pm);
                    if (result1){
                        long dataSize = imagePacker.finalizePacking();
                    }
                }
                fos.flush();
                fos.close();
                valuesBucket.clear();
                //解除独占
                valuesBucket.putInteger("is_pending",0);
                helper.update(uri,valuesBucket,null);
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 逐行扫描 清除边界空白。
     *
     * @param pm
     * @param blank 边距留多少个像素
     * @return
     */
    private PixelMap clearBlank(PixelMap pm, int blank) {
        ImageInfo imageInfo = pm.getImageInfo();
        Size size = imageInfo.size;
        int HEIGHT = size.height;
        int WIDTH = size.width;
        int top = 0, left = 0, right = WIDTH, bottom = HEIGHT;
        int[] pixs = new int[WIDTH];
        boolean isStop;
        for (int y = 0; y < HEIGHT; y++) {
            pm.readPixels(pixs, 0, WIDTH, new Rect(0, y, WIDTH, 1));
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    top = y;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        for (int y = HEIGHT - 1; y >= 0; y--) {
            pm.readPixels(pixs, 0, WIDTH, new Rect(0, y, WIDTH, 1));
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    bottom = y;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }

        int scanHeight = bottom - top;
        pixs = new int[scanHeight];
        for (int x = 0; x < WIDTH; x++) {
            pm.readPixels(pixs, 0, 1, new Rect(x, top, 1, scanHeight));
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    left = x;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        for (int x = WIDTH - 1; x > 0; x--) {
            pm.readPixels(pixs, 0, 1, new Rect(x, top, 1, scanHeight));
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    right = x;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        if (blank < 0) {
            blank = 0;
        }
        left = Math.max(left - blank, 0);
        top = Math.max(top - blank, 0);
        right = Math.min(right + blank, WIDTH - 1);
        bottom = Math.min(bottom + blank, HEIGHT - 1);
        PixelMap.InitializationOptions opt = new PixelMap.InitializationOptions();
        opt.size = new Size(getWidth(), getHeight());
        opt.pixelFormat = PixelFormat.ARGB_8888;
        return PixelMap.create(pm, new Rect(left, top, right - left, bottom - top), opt);
    }

    public void setPaintColor(Color paintColor) {
        mPaint.setColor(paintColor);
    }
//
//    public Color getPaintColor() {
//        return mPaint.getColor();
//    }

    public void setPaintWidth(int mMinWidth, int mMaxWidth) {
        if (mMinWidth > 0 && mMaxWidth > 0 && mMinWidth <= mMaxWidth)
            pointUtil.setWidth(mMinWidth, mMaxWidth);
    }

//    public int getPaintWidth() {
//        return pointUtil.getWidth()[0];
//    }

    public void clear() {
        isSign = false;
        if (mPixelMap != null && !mPixelMap.isReleased()) {
            mPixelMap.release();
        }
        mPixelMap = null;
        ensureSignaturePixelMap();
        invalidate();
    }

    public boolean isSign() {
        return isSign;
    }
}
